/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// TODO This class needs some rework as there are some good opportunities for code reuse.
public class OptionsParser {

	public static final String PARAM_PREFIX = "--";
	public static final String SOOT_ARGUMENT_PREFIX = "-";
	public static final String ARGUMENT_SEPARATOR = ":";

	/*
	 * Boolean Parameters
	 */
	private static boolean fullReport = false;
	private static boolean singleFile = false;
	private static boolean showUsage = false;
	private static boolean withTrapInspection = true;
	private static boolean runtimeExceptions = true;
	private static boolean analyzeStandardLibraries = true;
	private static boolean staticInitializers = false;
	private static boolean reportRuntimeLibraries = false;
	private static boolean autoGenerated = false;
	private static boolean excludeReflection = false;

	/*
	 * Single String parameters
	 */
	private static String callGraph = "cha"; 
	private static String reportPath = "reports";
	private static String mainClass = "";
	private static String mainMethod = "";

	/*
	 * String list parameters
	 */
	private static List<String> classIncludeList = new ArrayList<String>();
	private static List<String> classExcludeList = new ArrayList<String>();
	private static List<String> packageExcludeList = new ArrayList<String>();
	private static List<String> classReportIncludeList = new ArrayList<String>();
	private static List<String> packageReportIncludeList = new ArrayList<String>();
	private static List<String> classPath = new ArrayList<String>();

	/*
	 * Parameters representations
	 */

	public static final String SHOW_USAGE_PARAM = "help";

	public static final String WITH_TRAP_INSPECTION_PARAM = "handler_inspection";
	public static final String CALL_GRAPH_PARAM = "call_graph";
	public static final String INCLUDE_CLASSES_PARAM = "include_classes";
	public static final String EXCLUDE_CLASSES_PARAM = "exclude_classes";
	public static final String EXCLUDE_PACKAGE_PARAM = "exclude_package";
	public static final String EXCLUDE_REFLECTION_PARAM = "exclude_reflection";
	public static final String RUNTIME_EXCEPTIONS_PARAM = "runtime_exceptions";
	public static final String ANALYZE_STANDARD_LIBRARIES_PARAM = "analyze_std_libs";
	public static final String STATIC_INITIALIZERS_PARAM = "static_initializers";
	public static final String AUTOGENERATED_PARAM = "autogenerated_methods";

	public static final String SINGLE_FILE_PARAM = "single_file";
	public static final String FULL_REPORT_PARAM = "full_report";
	public static final String REPORT_PATH_PARAM = "report_path";
	public static final String INCLUDE_CLASSES_ON_REPORT_PARAM = "report_classes";
	public static final String INCLUDE_PACKAGE_ON_REPORT_PARAM = "report_packages";
	public static final String REPORT_STANDARD_LIBRARIES_PARAM = "report_std_libs";
	public static final String CLASS_PATH_PARAM = "classpath";
	public static final String MAIN_METHOD_PARAM = "main_meth";



	public static boolean isWithTrapInspection() {
		return withTrapInspection;
	}

	public static void setWithTrapInspection(boolean withTrapInspection) {
		OptionsParser.withTrapInspection = withTrapInspection;
	}

	public static String getCallGraph() {
		return callGraph;
	}

	public static void setCallGraph(String call_graph) {
		OptionsParser.callGraph = call_graph;
	}

	public static boolean isSingleFile() {
		return singleFile;
	}

	public static void setSingleFile(boolean singleFile) {
		OptionsParser.singleFile = singleFile;
	}

	public static boolean isFullReport() {
		return fullReport;
	}

	public static void setFullReport(boolean fullReport) {
		OptionsParser.fullReport = fullReport;
	}

	public static boolean isShowUsage() {
		return showUsage;
	}

	public static void setShowUsage(boolean showUsage) {
		OptionsParser.showUsage = showUsage;
	}

	public static String getReportPath() {
		return reportPath;
	}

	public static void setReportPath(String reportPath) {
		OptionsParser.reportPath = reportPath;
	}

	public static List<String> getClassIncludeList() {
		return classIncludeList;
	}


	public static void setClassIncludeList(List<String> includeList) {
		OptionsParser.classIncludeList = includeList;
	}

	public static List<String> getPackageReportIncludeList() {
		return packageReportIncludeList;
	}

	public static void setPackageReportIncludeList(List<String> includePackageList) {
		OptionsParser.packageReportIncludeList = includePackageList;
	}

	public static List<String> getClassExcludeList() {
		return classExcludeList;
	}

	public static void setClassExcludeList(List<String> classExcludeList) {
		OptionsParser.classExcludeList = classExcludeList;
	}

	public static List<String> getPackageExcludeList() {
		return packageExcludeList;
	}

	public static void setPackageExcludeList(List<String> packExcludeList) {
		OptionsParser.packageExcludeList = packExcludeList;
	}

	public static void setAnalyzeRuntime(boolean analyzeRuntime) {
		OptionsParser.runtimeExceptions = analyzeRuntime;
	}

	public static boolean isAnalyzeRuntime() {
		return OptionsParser.runtimeExceptions;
	}

	public static List<String> getClassReportIncludeList() {
		return classReportIncludeList;
	}

	public static void setClassReportIncludeList(List<String> classReportIncludeList) {
		OptionsParser.classReportIncludeList = classReportIncludeList;
	}

	public static boolean isReportStandardLibraries(){
		return OptionsParser.reportRuntimeLibraries;
	}

	public static void setReportStandardLibraries(boolean reportRuntimeLibraries) {
		OptionsParser.reportRuntimeLibraries = reportRuntimeLibraries;
	}

	public static boolean isAnalyzeStandardLibraries() {
		return OptionsParser.analyzeStandardLibraries;
	}

	public static void setAnalyzeStandardLibraries(boolean analyzeStandardLibraries) {
		OptionsParser.analyzeStandardLibraries = analyzeStandardLibraries;
	}

	public static boolean isStaticInitializers() {
		return OptionsParser.staticInitializers;
	}

	public static void setStaticInitializers(boolean staticInitializers) {
		OptionsParser.staticInitializers = staticInitializers;
	}

	public static boolean isAutoGenerated() {
		return OptionsParser.autoGenerated;
	}

	public static void setAutoGenerated(boolean autoGenerated) {
		OptionsParser.autoGenerated = autoGenerated;
	}

	public static boolean isExcludeReflection() {
		return excludeReflection;
	}

	public static void setExcludeReflection(boolean excludeReflection) {
		OptionsParser.excludeReflection = excludeReflection;
	}

	public static List<String> getClasspath() {
		return classPath;
	}

	public static void setClasspath(List<String> classpath) {
		OptionsParser.classPath = classpath;
	}

	public static String getMainClass() {
		return mainClass;
	}

	public static void setMainClass(String mainClass) {
		OptionsParser.mainClass = mainClass;
	}

	public static String getMainMethod() {
		return mainMethod;
	}

	public static void setMainMethod(String mainMethod) {
		OptionsParser.mainMethod = mainMethod;
	}

	public static void parse(String[] args) throws ParsingException {
		List<String> argsList = new ArrayList<String>(Arrays.asList(args));
		setMainClass(argsList.get(0));
		argsList.remove(0);
		for (String str : argsList) {
			if(isValidParameter(str, WITH_TRAP_INSPECTION_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						WITH_TRAP_INSPECTION_PARAM, "setWithTrapInspection");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(WITH_TRAP_INSPECTION_PARAM));
			} else if(isValidParameter(str, REPORT_PATH_PARAM)){
				String path = str.replaceFirst(parameterString(REPORT_PATH_PARAM), "");
				OptionsParser.setReportPath(path);
			} else if(isValidParameter(str, CALL_GRAPH_PARAM)){
				String cg = str.replaceFirst(parameterString(CALL_GRAPH_PARAM), "");
				OptionsParser.setCallGraph(cg);
			} else if(isValidParameter(str, FULL_REPORT_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						FULL_REPORT_PARAM, "setFullReport");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(FULL_REPORT_PARAM));
			} else if(isValidParameter(str, RUNTIME_EXCEPTIONS_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						RUNTIME_EXCEPTIONS_PARAM, "setAnalyzeRuntime");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(RUNTIME_EXCEPTIONS_PARAM));
			} else if(isValidParameter(str, SINGLE_FILE_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						SINGLE_FILE_PARAM, "setSingleFile");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(SINGLE_FILE_PARAM));
			} else if(isValidParameter(str, REPORT_STANDARD_LIBRARIES_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						REPORT_STANDARD_LIBRARIES_PARAM, "setReportStandardLibraries");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(REPORT_STANDARD_LIBRARIES_PARAM));
			} else if(isValidParameter(str, ANALYZE_STANDARD_LIBRARIES_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						ANALYZE_STANDARD_LIBRARIES_PARAM, "setAnalyzeStandardLibraries");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(ANALYZE_STANDARD_LIBRARIES_PARAM));
			} else if(isValidParameter(str, STATIC_INITIALIZERS_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						STATIC_INITIALIZERS_PARAM, "setStaticInitializers");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(STATIC_INITIALIZERS_PARAM));
			} else if(isValidParameter(str, AUTOGENERATED_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						AUTOGENERATED_PARAM, "setAutoGenerated");
				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(AUTOGENERATED_PARAM));
			} else if(isValidParameter(str, EXCLUDE_REFLECTION_PARAM)){
				boolean processed = OptionsParser.processBooleanValue(str, 
						EXCLUDE_REFLECTION_PARAM, "setExcludeReflection");

				if(!processed) throw new ParsingException("Unable to parse arguments for " + parameterString(EXCLUDE_REFLECTION_PARAM));
				else {
					// XXX Not sure if this should stay here or in the OptionsParser clients
					if(isExcludeReflection()){
						packageExcludeList.add("java.lang.reflect.*");
					}
				}
			}else if(isValidParameter(str, INCLUDE_CLASSES_PARAM)){
				OptionsParser.classIncludeList.addAll(processCommaSeparatedList(str, INCLUDE_CLASSES_PARAM));
			} else if(isValidParameter(str, INCLUDE_PACKAGE_ON_REPORT_PARAM)){
				OptionsParser.packageReportIncludeList.addAll(processCommaSeparatedList(str, INCLUDE_PACKAGE_ON_REPORT_PARAM));
			} else if(isValidParameter(str, EXCLUDE_CLASSES_PARAM)){
				OptionsParser.classExcludeList.addAll(processCommaSeparatedList(str, EXCLUDE_CLASSES_PARAM));
			} else if(isValidParameter(str, EXCLUDE_PACKAGE_PARAM)){
				OptionsParser.packageExcludeList.addAll(processCommaSeparatedList(str, EXCLUDE_PACKAGE_PARAM));
			} else if(isValidParameter(str, INCLUDE_CLASSES_ON_REPORT_PARAM)){
				OptionsParser.classReportIncludeList.addAll(processCommaSeparatedList(str, INCLUDE_CLASSES_ON_REPORT_PARAM));
			} else if(isValidParameter(str, CLASS_PATH_PARAM)){
				OptionsParser.classPath.addAll(processCommaSeparatedList(str, CLASS_PATH_PARAM));
			} else if(str.startsWith(OptionsParser.PARAM_PREFIX + SHOW_USAGE_PARAM)){
				OptionsParser.setShowUsage(true);
			}else {
				throw new ParsingException("Unable to parse token: " + str);
			}

		}

	}

	private static boolean isValidParameter(String arg, String argName){
		return arg.startsWith(OptionsParser.PARAM_PREFIX + argName + OptionsParser.ARGUMENT_SEPARATOR);
	}

	private static String parameterString(String paramName){
		return PARAM_PREFIX + paramName + ARGUMENT_SEPARATOR;
	}

	private static List<String> processCommaSeparatedList(String val,String paramName){
		String param = val.replaceFirst(parameterString(paramName), "");
		String[] list = param.split(",");
		List<String> retList = new ArrayList<String>();
		for (String c : list) {
			retList.add(c);
		}
		return retList;
	}

	private static boolean processBooleanValue(String val,String paramName,String setterMethName){
		boolean processed = false;
		String bool = val.replaceFirst(parameterString(paramName), "");
		try {

			Method m = OptionsParser.class.getMethod(setterMethName, boolean.class);
			if(bool.equals("true")){
				m.invoke(null, Boolean.TRUE);
			} else if(bool.equals("false")){
				m.invoke(null, Boolean.FALSE);
			}
			processed = true;
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return processed;
	}


}
